<?php
require_once('Interfaces/AIS_JsonRpc2Service.php');
require_once('AIS_JsonRpc2SMDImpl.php');
require_once dirname(__FILE__).('/../Validate/AIS_ObjectTypeCompatibilityValidator.php');
require_once('AIS_JsonRpcErrorCodes.php');
require_once 'AIS_ServiceException.php';
require_once dirname(__FILE__).'/../DocParsing/AIS_DocCommentFunction.php';

/**
 * The JsonRpc Error Structure that is sent to the client when an Error occured
 *
 */
class AIS_JsonRpcErrorResponse
{
	/**
	 * Code of the Error Message
	 *
	 * @var int
	 */
	public $code;
	/**
	 * The Error Message
	 *
	 * @var string
	 */
	public $message;
	/**
	 * An Additional Json-Object containing arbitrary data
	 *
	 * @var object
	 */
	public $data;
}
/**
 * The Response Structure of a JsonRpc call that is sent to the client
 * if the request was successfull
 *
 */
class AIS_JsonRpcResultResponse
{
	/**
	 * The JsonRpc-Version
	 *
	 * @var string
	 */
	public $jsonrpc = "2.0";
	/**
	 * The id. Can be any valid json-object
	 *
	 * @var object
	 */
	public $id = NULL;
}
/**
 * Class to store and Retreive ReflectionMethds
 *
 */
class AIS_ClassReflectionMethods
{
	/**
	 * Add a new Method to store
	 *
	 * @param ReflectionMethod $method
	 */
	public function addMethod($method)
	{
		$params = $method->getParameters();
		$nrOfParams = sizeof($params);
		$name = $method->getName();
		$this->methodNames[$name] = 1;
		$name = $name.$nrOfParams;
		$this->methods[$name] = $method;
	}
	/**
	 * Checks if a method with that name was stored
	 *
	 * @param string $methodName
	 * @param bool Method exists or not
	 */
	public function methodExists($methodName)
	{
		return array_key_exists($methodName, $this->methodNames);
	}
	/**
	 * Checks if a method with that name and amount of arguments exist
	 *
	 * @param string $methodName
	 * @param int $args
	 * @return bool Method exists or not
	 */
	public function methodWithArgsExist($methodName, $args)
	{
		return array_key_exists($methodName.$args, $this->methods);
	}
	/**
	 * Returns the method with given name and amount of arguments
	 *
	 * @param string $methodName
	 * @param string $args
	 * @return ReflectionMethod the reflection Method with given name and arguments, or NULL if not found
	 */
	public function getMethod($methodName, $args)
	{
		return $this->methods[$methodName.$args];
	}
	private $methodNames = array();
	private $methods = array();
}


/**
 * Implementation of AIS_JsonRpc2Service
 * 
 *
 */
class AIS_JsonRpc2ServiceImp implements AIS_JsonRpc2Service 
{
	
	function __construct() {
		
	}
	/**
	 * Sets the class that containing the RPC methods
	 *
	 * @param string $className
	 */
	public function setClass($className)
	{
		$this->reflectionMethods = new AIS_ClassReflectionMethods();
		$rc = new ReflectionClass($className);
		$this->instance = $rc->newInstance();
	    $rm = $rc->getMethods();
		foreach ($rm as $method)
		{
			$this->reflectionMethods->addMethod($method);
		}
	}
	/**
	 * Returns the Json Response as a string
	 *
	 * @param string $request
	 * @return string
	 */
	public function getJsonResponse($request)
	{
		$req = utf8_encode($request);
		$req = json_decode($req);
		$errors = $this->checkForPrimitiveErrors($req);
		if ($errors != FALSE)
		{
			return $errors;
		}
		$nrOfParams = 0;
		if ($req->params != NULL)
		{
			$nrOfParams = count($req->params);
		}
		$rm = $this->reflectionMethods->getMethod($req->method, $nrOfParams);
		$data = NULL;
		if ($nrOfParams == 0)
		{
			$data = $rm->invoke($this->instance);
		}
		else
		{
			if (!$this->methodParamsValidator->validate($rm,$req->params))
			{
				return $this->createErrorResponse(AIS_JsonRpcErrorCodes::$invalidParams, $this->methodParamsValidator->getErrorMessage());
			}
			try {
				$data = $rm->invokeArgs($this->instance, $req->params);
			}
			catch (AIS_ServiceException $ex)
			{
				return $this->createErrorResponse(AIS_JsonRpcErrorCodes::$methodException, $ex->getData());
			}
			catch (Exception $exc)
			{
				return $this->createErrorResponse(AIS_JsonRpcErrorCodes::$methodException, $exc->getMessage());
			}
			
		}
		$resp = new AIS_JsonRpcResultResponse();
		$resp->result = $data;
		$resp->id = $req->id;
		return json_encode($resp);
	}
	/**
	 * 
	 * @return boolean
	 * @param mixed $req
	 */
	private function checkForPrimitiveErrors($req)
	{
		if ($req == null)
		{
			return $this->createErrorResponse(AIS_JsonRpcErrorCodes::$parseError, "Invalid JSON. An error occurred on the server while parsing the JSON text.");
		}
		if ($req->jsonrpc == NULL || $req->method == NULL)
		{
			return $this->createErrorResponse(AIS_JsonRpcErrorCodes::$invalidRequest, "The received JSON not a valid JSON-RPC Request.");
		}
		if ($req->jsonrpc != "2.0")
		{
			return $this->createErrorResponse(AIS_JsonRpcErrorCodes::$invalidRequest, "The server only supports jsonrpc 2.0.");
		}
		if (!$this->reflectionMethods->methodExists($req->method))
		{
			return $this->createErrorResponse(AIS_JsonRpcErrorCodes::$methodNotFound, "The requested remote-procedure does not exist / is not available.");
		}
		$nrOfParams = 0;
		if ($req->params != NULL)
		{
			if (!(is_array($req->params)))
			{
				return $this->createErrorResponse(AIS_JsonRpcErrorCodes::$invalidParams, "Method Parameters must be an array.");
			}
			else
			{
				$nrOfParams = count($req->params);
			}
		}
		if (!$this->reflectionMethods->methodWithArgsExist($req->method, $nrOfParams))
		{
			return $this->createErrorResponse(AIS_JsonRpcErrorCodes::$invalidParams, "Method Parameters have an invalid amount of Parameters. Received: ".$req->params);
		}
		return FALSE;
	}
	/**
	 * Sets a callback function that is called whenever an Json-Error-Response will be generated,
	 * for example, if the client tried to call a function that doesn't exist, or if some parameters
	 * had an illegal type. The function receives an AIS_JsonRpcErrorCode as argument. You can modify
	 * this parameter and in this way change the error response the client will receive. The data-Property
	 * can either be a string or of type AIS_ObjectTypeCompatibilityValidatorError if the json-object sent by
	 * the client had some illegal properties.
	 * 
	 *
	 * @param mixed $function
	 */
	public function setErrorCallback($function)
	{
		$this->function = $function;
	}
	/**
	 * Enter description here...
	 *
	 * @param unknown_type $validator
	 */
	public function setMethodParamsValidator($validator)
	{
		$this->methodParamsValidator = $validator;
	}
	/**
	 * Returns a json_encoded AIS_JsonRpcErrorResponse
	 *
	 * @param AIS_JsonRpcErrorCode $standardError The error that happened
	 * @param mixed $data arbitrary data
	 * @return string
	 */
	protected function createErrorResponse($standardError, $data)
	{
		$err = new AIS_JsonRpcErrorResponse();
		$err->code= $standardError->code;
		$err->message = $standardError->message;
		$err->data = $data;
		$resp = new AIS_JsonRpcResultResponse();
		$resp->error = $err;
		if ($this->function)
		{
			$this->function($resp);
		}
		return json_encode($resp);
	}
	
	private $instance;
	private $reflectionMethods;
	private $methodParamsValidator;
	private $function;
}
?>